`timescale 1ns / 1ps
//////////////////////////////////////////////////////////////////////////////////
// Company: CBICR, Tsinghua Univ.
// Engineer: Hongyi Li
// 
// Create Date: 2024/12/23 11:58:54
// Design Name: 
// Module Name: Buffer Write (For One Direction)
// Project Name: 
// Target Devices: 
// Tool Versions: 
// Description: 
// 
// Dependencies: 
// 
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
// 
//////////////////////////////////////////////////////////////////////////////////

module BufferWr
#(
    parameter DataWidth = 'd32,
    parameter FifoDepth = 'd4,
    parameter VCNumber  = 'd4
)(
    input                                        clk, rst_n,
    input  [DataWidth-1:0]                       i_data,
    input  [VCNumber-1:0]                        i_pop_reqs,
    output [(DataWidth * VCNumber)-1:0]          o_data,
    output [VCNumber-1 :0]                       o_valid,
    output [($clog2(FifoDepth) * VCNumber)-1 :0] o_credits,
    output [(DataWidth * VCNumber)-1:0]          o_head,
    output [(5 * VCNumber)-1:0]                  o_directions
);

reg                 last_is_tail;
reg                 state;
reg [VCNumber-1:0]  last_push_reqs;
reg [(DataWidth * VCNumber)-1:0] cur_head;

wire [VCNumber-1:0]  push_reqs;
wire                 is_head;
wire                 is_tail;
wire [VCNumber-1:0]  is_empty; // channel-wise
wire [VCNumber-1:0]  front_is_head;
wire                 switch_state;
/* Buffer-Write ASM
 * 
 * State 0: Idle until Finding the Head [0] -> [1]
 * State 1: Processing until Tail       [1] -> [0]
 */

assign is_tail = (i_data[3:0] == 4'hf);
assign is_head = (i_data[3:0] == 4'h0) && (|i_data[(19 + VCNumber):20]);
assign switch_state = (~is_head & last_is_tail & state) | (is_head & ~is_tail & ~state);
assign push_reqs = is_head ? i_data[(19 + VCNumber):20] : (last_is_tail ? 0 : last_push_reqs);

// Input-Related Reg Update
always @(posedge clk) begin
    if (!rst_n) begin
        state <= 0;
        last_is_tail <= 0;
        last_push_reqs <= 0;
    end else if (switch_state) begin
        state <= ~state;
        last_is_tail <= is_tail;
        if (is_head) last_push_reqs <= i_data[(19 + VCNumber):20]; // ...-[]-[]-H-B-B-T-[]-...
        else last_push_reqs <= 0; // -T-[]-...
    end else begin
        state <= state;
        last_is_tail <= is_tail;
        if (is_head && state) last_push_reqs <= i_data[(19 + VCNumber):20]; // ...-B-T-H-B-B-...
        else last_push_reqs <= last_push_reqs; // ...-B-B- ... or ...-[]-[]-...
    end
end


// Generate Fifos
genvar i;
generate
    for (i = 0; i < VCNumber; i = i + 1) begin

        assign front_is_head[i] = (o_data[(DataWidth*i)+3 : (DataWidth*i)] == 4'h0) && 
                                (|o_data[(DataWidth*i)+VCNumber+19 : (DataWidth*i)+20]);

        // One Virtual Channel
        SyncFifo #(
            .width(DataWidth),
            .depth(FifoDepth)
        ) SyncFifoU (
            .clk(clk),
            .rst_n(rst_n),
            .i_push_req(push_reqs[i]),
            .i_pop_req(i_pop_reqs[i]),
            .i_data(i_data),
            .o_empty(is_empty[i]),
            .o_credits(o_credits[(($clog2(FifoDepth))*(i+1))-1 : (($clog2(FifoDepth))*i)]),
            .o_data(o_data[(DataWidth*(i+1))-1:(DataWidth*i)])
        );

        always @(posedge clk) begin
            if (!rst_n) 
                cur_head[(DataWidth*(i+1))-1 : (DataWidth*i)] <= 0;
            else if (front_is_head[i])
                cur_head[(DataWidth*(i+1))-1 : (DataWidth*i)] <= o_data[(DataWidth*(i+1))-1 : (DataWidth*i)];
            else
                cur_head[(DataWidth*(i+1))-1 : (DataWidth*i)] <= cur_head[(DataWidth*(i+1))-1 : (DataWidth*i)];
        end

        assign o_valid[i] = (~is_empty[i]) & front_is_head[i];
        assign o_head[DataWidth*(i+1)-1 : DataWidth*i] = front_is_head ? o_data[(DataWidth*(i+1))-1 : (DataWidth*i)] : cur_head;
    end
endgenerate

endmodule